from .exception import GameException
from .pos_direct import GamePosition
from typing import List, Tuple, Union
from .game_gui import GameGUI
import _thread

import numpy as np

reward_map = {'$': 1.0, '.': 0.0, '#': -1.0, '~': -0.2}


def parse_reward(data_type: str) -> float:
    """
    检查当前位置，可以获得的reward大小
    :param data_type: '~' '.' '#' '$'
    :return: reward值
    """
    return reward_map[data_type]


class Game:
    """
    游戏类
    init：构造游戏的相关地图
    start：需要传入用户的相关信息 然后开始启动游戏 返回vision
    move(Action)：传入用户需要走的方向 返回(vision, reward)
    is_finished：判定游戏是否结束 返回 <bool>
    """

    def __init__(self, map_data: np.ndarray, vision_range: int, gui: Union[GameGUI, None] = None):
        """
        游戏类构造函数
        （坐标值，y轴向下，x轴向右）
        构造地图和地形
        """
        self.height = map_data.shape[0]
        self.width = map_data.shape[1]
        self.vision_range = vision_range
        self.player_pos: Union[GamePosition, None] = None
        self.gui: GameGUI = gui
        self.finshed = False
        self.data = map_data

    def vision(self) -> np.ndarray:
        vision = []
        start_y = self.player_pos.y - self.vision_range // 2
        start_x = self.player_pos.x - self.vision_range // 2
        for i in range(self.vision_range):
            y = start_y + i
            row = []
            for j in range(self.vision_range):
                x = start_x + j
                row.append(self.get_data(y, x))
            vision.append(row)
        return np.array(vision).reshape((-1, self.vision_range, self.vision_range))

    def get_data(self, py, px) -> str:
        """
        获取到具体坐标的数值
        （坐标值，y轴向下，x轴向右）
        :param py: 纵坐标
        :param px: 横坐标
        :return:
        """
        if px < 0 or px >= self.width or py < 0 or py >= self.height:
            return '~'
        return self.data[py][px]

    def start(self, player_pos: GamePosition) -> Union[np.ndarray, None]:
        """
        :param player_pos: 初始玩家坐标
        :return: 返回玩家看到的视野 如果不合法 返回None
        """
        py = player_pos.y
        px = player_pos.x
        if self.get_data(py, px) != '.':
            return None
        self.finshed = False
        self.player_pos = GamePosition(py, px)
        if self.gui is not None:
            self.gui.start_maze(self.player_pos)
        return self.vision()

    def move(self, action: str) -> Tuple[np.ndarray, float]:
        """
        移动用户
        :param action: 采取的决策
        :return:
        """
        ny = self.player_pos.y
        nx = self.player_pos.x
        if action == 'UP':
            ny = self.player_pos.y - 1
        elif action == 'DOWN':
            ny = self.player_pos.y + 1
        elif action == 'LEFT':
            nx = self.player_pos.x - 1
        elif action == 'RIGHT':
            nx = self.player_pos.x + 1
        else:
            pass
        # 尝试获取到可能的接下来的数据类型取值next_data_type
        next_data_type = self.get_data(ny, nx)
        reward = parse_reward(next_data_type)
        if next_data_type != '~':
            # 只要用户没有朝边界走 那么用户就可以进行移动
            self.player_pos.y = ny
            self.player_pos.x = nx
        # 返回用户的视野 与 最终的reward
        if next_data_type == '#' or next_data_type == '$':
            self.finshed = True
        return self.vision(), reward

    def is_finished(self) -> bool:
        return self.finshed

    def display(self):
        """
        之所以要使用到action 是因为当前的mazeGUI需要使用到action
        :param action: 上下左右移动
        :return:
        """
        if self.gui is None:
            print(self.vision())
        else:
            # self.gui.display(self.player_pos)
            self.gui.display(self.player_pos)
